#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<cerrno>
#include<cstring>
#include<iostream>
#include<cassert>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string>
#include<cstdio>

using namespace std;

#define PATHNAME "./"
#define PROJID 0664
#define SIZE 4096

const string str1 = "./fifo1";
const string str2 = "./fifo2";

key_t GetKey()
{
  key_t key = ftok(PATHNAME,PROJID);
  if(key == -1)
  {
    cout << "GetKey occur fail , errno : " << errno << " " << strerror(errno) << endl;
    exit(1);
  }
  return key;
}

int SheGetHelper(key_t key,int size,int shmflag)
{
  int shmid = shmget(key,size,shmflag);;
  if(shmid == -1)
  {
    cout << "shmget occur fail , errno : " << errno << " " << strerror(errno) << endl; 
    exit(2);
  }
  return shmid;
}

int ShmCreat(key_t key,int size)
{
  int shmid = shmget(key,size,IPC_CREAT | IPC_EXCL | 0664);
  assert(shmid != -1);
  (void)shmid;
  return shmid;
}

int ShmGet(key_t key,int size)
{
  int shmid = shmget(key,size,IPC_CREAT);
  assert(shmid != -1);
  (void)shmid;
  return shmid;
}

char* AttachShm(int shmid)
{
  char* Start = (char*)shmat(shmid,nullptr,0);
  if(Start == (char*)-1)
  {
    cout << "shmat occur fail , errno : " << errno << " " << strerror(errno) << endl;
    return nullptr;
  }
  return Start;
}

int DetachShm(char* Start)
{
  int n = shmdt(Start);
  assert(n != -1);
  (void)n;
  return n;
}

void DeteleShm(int shmid)
{
  int n = shmctl(shmid,IPC_RMID,nullptr);
  assert(n != -1);
  (void)n;
}

#define CLIENT 0
#define SERVER 1

class shm
{
public:
  shm(int type)
    :_type(type)
  {
    //获取key
    key_t key = GetKey();

    //创建or获取
    if(_type == SERVER) _shmid = ShmCreat(key,SIZE);
    else _shmid = ShmGet(key,SIZE);

    //关联
    _Start = AttachShm(_shmid);
    
    //匿名管道只能用于父子进程通信，原因自行复习匿名管道的原理，所以这里两个管道都要命名管道
    //服务端主导发送，客户端主导接收的管道
    if(_type == SERVER)
    {
       int n1 = mkfifo(str1.c_str(),0666);
       assert(n1 != -1);
       (void)n1;
       cout << "命名管道1开启成功" << endl;
    }

    //客户端主导消息发送，服务端主导消息接收的管道
    if(_type == SERVER)//两个管道都是SEERVER端开启创建，不然一个SERVER一个CLIENT，会导致赋值不到位问题
    {
       int n = mkfifo(str2.c_str(),0666);
       assert(n != -1);
       (void)n;
       cout << "命名管道2开启成功" << endl;
    }

    if(_type == SERVER) _server_namepipe = open(str1.c_str(),O_WRONLY);//服务端写
    else _server_namepipe = open(str1.c_str(),O_RDONLY);//客户端读
    cout << "命名管道1赋值成功 " << _server_namepipe << endl;

    if(_type == CLIENT) _client_namepipe = open(str2.c_str(),O_WRONLY);
    else _client_namepipe = open(str2.c_str(),O_RDONLY);
    cout << "命名管道2赋值成功 " <<  _client_namepipe << endl;
  }

  char* GetStart()
  {
    return _Start;
  }

  int Get_client_namepipe()
  {
    return _client_namepipe;
  }

  int Get_server_pipe()
  {
    return _server_namepipe;
  }

  ~shm()
  {
    //去关联
    DetachShm(_Start);

    //客户端要删除共享内存
    if(_type == SERVER) DeteleShm(_shmid);

    //关闭管道
    close(_client_namepipe);
    close(_server_namepipe);

    if(_type == SERVER)
    {
      unlink(str1.c_str());
      unlink(str2.c_str());
    }
  }
  
private:
  int _type;
  int _shmid;
  char* _Start;
  int _client_namepipe;//客户端向服务端发送信息，表明写入共享内存的内容完成，可以读取
  int _server_namepipe;//服务端向客户端发生信息，表面读取共享内存的内容完成，可以写入
};
